<script setup lang="ts">
import {
  getIconifySetIcons,
  StIconIconify,
  StIconIconifyOffline,
  searchIconifyIcons,
  iconifyIconExists,
} from 'st-common-ui-vue3'
import type {IconifySetIcons} from 'st-common-ui-vue3'
import {computed, onBeforeMount, onBeforeUnmount, ref, VNodeChild} from "vue"
import {
  NPagination,
  NTabs,
  NTabPane,
  NGrid,
  NGi,
  SelectOption,
  SelectProps,
  NInput,
  NButton,
  NSpin,
  NScrollbar,
  useMessage,
  NEmpty,
} from 'naive-ui'
import {elSizeUtil} from 'st-common-ui-utils'
import Search from "@iconify-icons/tabler/search"
import 'naive-ui/es/message/styles/index'

const message = useMessage()

/**
 * 组件配置选项
 */
defineOptions({
  name: 'StnIconifyIconSelector',
})

/**
 * 组件参数
 */
const {
  setPrefixes = [],
  sets = {},
  setSelectedDefault = undefined,
  isOffline = false,
  iconSize = 24,
  iconSelectedColor = '#18a058',
  size = 'small',
  search = true,
  setSearchIpt = true,
  searchResultLimit = 64,
  searchIconNameNotIptTip = '请输入图标名称',
  setIptPlaceholder = '图标集名称',
  iconNamePlaceholder = '图标名称',
  setIptFlex = 2,
  iconNameIptFlex = 3,
  searchNotFoundTip = '没有找到相关图标',
  iconSetNullTip = '没有图标呢...',
  searchTabName = '搜索',
  iconListMaxHeight = '130px',
  iconListHeight = '130px',
  iconListScrollbarTrigger = 'hover',
  cols = '5 375:10 600:15 800:20 1200:30',
  responsive = 'self',
  itemResponsive = false,
  tabsPadding = 0,
  tabTrigger = 'click',
  tabPlacement = 'top',
  tabType = 'bar',
  pageSizeDefault = 30,
  pageSizes = [],
  pageSlot = 8,
  showQuickJumper = false,
  goto = () => '跳至',
  paginationSimple = false,
} = defineProps<{
  // 图标集前缀
  setPrefixes?: string[];
  // 由图标集图标名称列表为 value，图标集名称为 key 组成的对象
  sets?: Record<string, string[]>;
  // 默认选中的图标集
  setSelectedDefault?: string;
  // 是否使用离线版的 IconifyIcon 组件
  isOffline?: boolean;
  // 图标大小
  iconSize?: number;
  // 图标选中颜色
  iconSelectedColor?: string;
  // 整个组件的大小
  size?: 'small' | 'medium' | 'large';
  // 是否开启搜索功能
  search?: boolean;
  // 是否开启可以指定搜索图标的图标集
  setSearchIpt?: boolean;
  // 搜索结果数量限制，默认为 64，最小值为 32，最大值为 999
  searchResultLimit?: number;
  // 搜索图标名称未输入提示
  searchIconNameNotIptTip?: string;
  // 图标集输入框 placeholder
  setIptPlaceholder?: string;
  // 图标名称输入框 placeholder
  iconNamePlaceholder?: string;
  // 图标集输入框 flex 比例
  setIptFlex?: number;
  // 图标名称输入框 flex 比例
  iconNameIptFlex?: number;
  // 没有找到的提示
  searchNotFoundTip?: string;
  // 图标集为空的提示
  iconSetNullTip?: string;
  // 搜索 tab 名称
  searchTabName?: string;
  // 图标列表的高度
  iconListHeight?: number | string;
  // 图标列表的最大高度
  iconListMaxHeight?: number | string;
  // 图标列表滚动条的显示时机，'none' 表示一直显示
  iconListScrollbarTrigger?: 'hover' | 'none';
  // 图标显示的列数
  cols?: number | string;
  // 图标的响应式布局，'self' 根据自身宽度进行响应式布局，'screen' 根据屏幕断点进行响应式布局
  responsive?: 'self' | 'screen';
  // 图标元素是否可具有响应式宽度
  itemResponsive?: boolean;
  // 全部标签最左和最右的 padding
  tabsPadding?: number;
  // 标签切换的触发方式
  tabTrigger?: 'click' | 'hover';
  // 标签的位置，对于 'segment' 类型的 n-tabs 不生效
  tabPlacement?: 'left' | 'right' | 'top' | 'bottom';
  // 标签的类型
  tabType?: 'bar' | 'line' | 'card' | 'segment';
  // 每页显示图标数量的默认值
  pageSizeDefault?: number;
  // 可选的每页显示图标数量
  pageSizes?: number[];
  // 页码显示的个数
  pageSlot?: number;
  // 是否显示快速跳转
  showQuickJumper?: boolean;
  // 快速跳转的文本内容
  goto?: () => VNodeChild;
  // 每页显示图标数量选择器组件参数
  paginationSelectProps?: SelectProps;
  // 分页器是否显示为简单分页
  paginationSimple?: boolean;
}>()

/**
 * 组件双向数据绑定
 */
const value = defineModel<string>('value', {default: ''})

// 由图标集图标名称列表组成的对象
const iconLists = ref<Record<string, Array<string>>>({})
// 当前选中的图标集
const setCurrent = ref<string>(setPrefixes[0])
/**
 * 当前选中的图标集发生改变的处理函数
 */
const setCurrentChangeHandler = (value: string) => {
  setCurrent.value = value
  if (value !== searchTabName) searchIptSet.value = value
}
// 是否处于加载图片状态
const loading = ref(true)
/**
 * 向由图标集图标名称列表组成的对象中添加空白项
 */
const addEmptyItemToIconLists = () => {
  iconLists.value[''] = ['']
}
/**
 * 清除向由图标集图标名称列表组成的对象中添加的空白项
 */
const delEmptyItemFromIconLists = () => {
  Reflect.deleteProperty(iconLists.value, '')
}
// 判断第一个图标能否显示的定时器
let timer: NodeJS.Timeout | null = null
onBeforeMount(async () => {
  loading.value = true
  // 获取图标集的图标名称列表之前，添加一个空白 tab 占位，并将其设置为当前选中
  addEmptyItemToIconLists()
  setCurrentChangeHandler('')
  if (search) {
    // 清除空白 tab，然后重新添加一个空白 tab，以确保空白 tab 在所有 tab 项最后
    delEmptyItemFromIconLists()
    iconLists.value[searchTabName] = []
    addEmptyItemToIconLists()
  }
  delEmptyItemFromIconLists()
  iconLists.value = {
    ...iconLists.value,
    ...sets,
  }
  addEmptyItemToIconLists()
  // 发送请求获取指定的图标集的图标名称列表
  for (const setPrefix of setPrefixes) {
    const iconifySetIcons: IconifySetIcons = await getIconifySetIcons({prefix: setPrefix}) as IconifySetIcons
    delEmptyItemFromIconLists()
    iconLists.value[setPrefix] = iconifySetIcons?.uncategorized as string[]
    addEmptyItemToIconLists()
  }
  // 获取图标集的图标名称列表完成后，清除空白 tab
  delEmptyItemFromIconLists()
  // 设置默认选中的图标集
  setCurrentChangeHandler((setSelectedDefault ?? Reflect.ownKeys(sets)[0] ?? setPrefixes[0]) as string)
  // 记录定时器执行的次数
  let timerCnt = 0
  // 开启定时器
  timer = setInterval(() => {
    timerCnt++
    // 如果第一个图标能够显示
    if (iconifyIconExists((setCurrent.value + ':' + getCurrentIconList(iconLists.value[setCurrent.value])[0]) as string)) {
      loading.value = false
      clearInterval(timer as NodeJS.Timeout)
    } else if (timerCnt > 20) {
      // 如果超过 20 次，说明第一个图标无法显示
      loading.value = false
      clearInterval(timer as NodeJS.Timeout)
    }
  }, 100)
})
onBeforeUnmount(() => {
  if (timer) clearInterval(timer)
})

// 每页显示图标数量
const pageSize = ref<number>(pageSizeDefault)
// 当前选中图标集总页数
const setPageCount = computed(() => {
  const pageCount = Math.ceil(iconLists.value[setCurrent.value]?.length / pageSize.value)
  return pageCount || 0
})
// 当前所在图标页
const page = ref(1)
/**
 * 获取当前页的图标列表
 */
const getCurrentIconList = (iconList: string[]) => iconList.slice((page.value - 1) * pageSize.value, page.value * pageSize.value)

// 搜索 tab 是否处于加载状态
const searchLoading = ref(false)
// 输入的当前要搜索的图标集
const searchIptSet = ref<string>(setPrefixes[0])
// 输入的当前要搜索的图标名称
const searchIptIcon = ref<string>('')
/**
 * 搜索图标处理函数
 */
const searchIconHandler = async () => {
  if (!search) return
  if (searchIptIcon.value.length <= 0) {
    message.error(searchIconNameNotIptTip)
    return
  }
  searchLoading.value = true
  const res = await searchIconifyIcons({
    query: searchIptIcon.value,
    prefixes: setSearchIpt ? [searchIptSet.value] : [],
    limit: searchResultLimit
  })
  if (res.icons.length === 0) message.info(searchNotFoundTip)
  else setCurrent.value = searchTabName
  iconLists.value[searchTabName] = res.icons
  searchLoading.value = false
}

/**
 * 图标名称计算函数
 */
const iconNameComputer = (prefix: string, icon: string) => {
  return (prefix === searchTabName ? icon : prefix + ':' + icon)
}
/**
 * 选择图标处理函数
 */
const selectIconHandler = (prefix: string, icon: string) => {
  value.value = iconNameComputer(prefix, icon)
}
</script>

<template>
  <div class="stn-iconify-icon-selector">
    <div
      v-if="search"
      class="stn-iconify-icon-selector__search"
    >
      <n-input
        v-if="setSearchIpt"
        class="stn-iconify-icon-selector__search-ipt-set"
        :style="{flex: setIptFlex}"
        v-model:value="searchIptSet"
        :size="size"
        :placeholder="setIptPlaceholder"
        clearable
      />
      <n-input
        class="stn-iconify-icon-selector__search-ipt-icon"
        :style="{flex: iconNameIptFlex}"
        v-model:value="searchIptIcon"
        :size="size"
        :placeholder="iconNamePlaceholder"
        clearable
        @keyup.enter="searchIconHandler"
      />
      <n-button
        type="primary"
        ghost
        :size="size"
        @click="searchIconHandler"
      >
        <template #icon>
          <StIconIconifyOffline :icon="Search"/>
        </template>
      </n-button>
    </div>
    <n-spin :show="searchLoading || loading">
      <n-tabs
        :size="size"
        :tabs-padding="tabsPadding"
        :trigger="tabTrigger"
        :placement="tabPlacement"
        :type="tabType"
        animated
        :value="setCurrent"
        :default-value="setPrefixes[0]"
        @update:value="setCurrentChangeHandler"
      >
        <n-tab-pane
          v-for="(iconList, prefix) in iconLists"
          :key="prefix"
          :name="prefix"
        >
          <div v-show="iconList.length > 0">
            <n-scrollbar
              :style="{
                height: elSizeUtil.elSizePreHandler(iconListHeight),
                maxHeight: elSizeUtil.elSizePreHandler(iconListMaxHeight)
              }"
              :trigger="iconListScrollbarTrigger"
            >
              <n-grid
                :cols="cols"
                :responsive="responsive"
                :item-responsive="itemResponsive"
              >
                <n-gi v-for="(icon) in getCurrentIconList(iconList)" :key="icon">
                  <div
                    class="stn-iconify-icon-selector__icon-box"
                    :class="[value === iconNameComputer(prefix, icon) ? 'stn-iconify-icon-selector__icon-box--selected' : '']"
                    :style="{'--icon-selected-color': iconSelectedColor}"
                    @click="selectIconHandler(prefix, icon)"
                  >
                    <StIconIconifyOffline
                      v-if="isOffline"
                      :width="iconSize"
                      :height="iconSize"
                      :icon="iconNameComputer(prefix, icon)"
                    />
                    <StIconIconify
                      v-else
                      :width="iconSize"
                      :height="iconSize"
                      :icon="iconNameComputer(prefix, icon)"
                    />
                  </div>
                </n-gi>
              </n-grid>
            </n-scrollbar>
          </div>
          <n-empty
            v-show="iconList.length === 0"
            :size="size"
            :description="iconSetNullTip"
          ></n-empty>
        </n-tab-pane>
      </n-tabs>
    </n-spin>
    <div class="stn-iconify-icon-selector__pagination">
      <n-pagination
        v-show="setPageCount > 0"
        v-model:page="page"
        v-model:page-size="pageSize"
        :default-page-size="pageSizeDefault"
        :size="size"
        :page-count="setPageCount"
        :page-sizes="pageSizes"
        :page-slot="pageSlot"
        :show-size-picker="pageSizes && pageSizes.length > 0"
        :show-quick-jumper="showQuickJumper"
        :show-quick-jump-dropdown="false"
        :goto="goto"
        :select-props="paginationSelectProps ?? {
          menuSize: size,
          renderLabel: (option: SelectOption) => `${option.value} / 页`,
        }"
        :simple="paginationSimple"
      />
    </div>
  </div>
</template>

<style scoped lang="scss">
.stn-iconify-icon-selector {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: 0.5rem;

  .stn-iconify-icon-selector__search {
    display: flex;
    align-items: center;
    margin-bottom: 0.5rem;
  }

  .stn-iconify-icon-selector__icon-box {
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    padding: 0.5rem;
    border: dashed 2px transparent;
    display: grid;
    grid-template-rows: 1fr;
    grid-template-columns: 1fr;
    justify-items: center;
    align-items: center;

    &:hover {
      background-color: rgba(46, 51, 56, .09);
      cursor: pointer;
    }
  }

  .stn-iconify-icon-selector__icon-box--selected {
    border: dashed 1.5px var(--icon-selected-color);
  }

  .stn-iconify-icon-selector__pagination {
    margin-top: 0.5rem;
  }
}
</style>
